package net.sf.odinms.exttools.doclet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

/**
 * <p>
 * Class for XHTML-formatted code generation to an underlying PrintWriter.
 * </p>
 * 
 * <p>
 * Initializes with a {@link FileOutputStream} to enable subclasses to write
 * directly to the file specified in the constructor via a variety of
 * <code>print()</code> and <code>println()</code> methods.
 * </p>
 * 
 * <p>
 * Methods are provided for simplified creation of (X)HTML tags by requesting
 * opening, closing, or empty (self-closing) tags and specifying the text.
 * Rather than error checking and throwing exceptions for incorrect
 * tags&mdash;which would add significant overhead and necessitate wrapping
 * method calls in a try-catch block&mdash;the caller is responsible for
 * verifying correctness of tags.
 * </p>
 * 
 * <p>
 * For {@link #open(String)} and {@link #empty(String)}, the argument could be
 * a simple tag name, or could even combine the tag name and all attributes in a
 * single string, which could be useful in reducing overhead for commonly-used
 * tag contents.
 * </p>
 * 
 * <p>
 * For {@link #open(String,Map)} and {@link #empty(String,Map)}, the first
 * argument should be a tag name only. Any {@link java.util.Map} subclass is
 * accepted for the second argument; using a {@link java.util.LinkedHashMap}
 * will preserve the insertion order of attribute-value pairs, whereas a
 * {@link java.util.TreeMap} sorts by attribute name.
 * </p>
 * 
 * <p>
 * For {@link #close(String)}, the argument should only be a tag name without
 * spaces; attributes are not allowed.
 * </p>
 * 
 * <p>
 * The intent is to encourage use of XHTML (see constant fields below) and valid
 * tags (which should always be in lowercase, although such usage is not
 * enforced). For any tag that includes attributes, the user should ensure that
 * each attribute is allowed for that tag type. See the second link below for a
 * reference of which attributes can be used on which tags; just click a tag
 * name to see a list of required and optional attributes.
 * </p>
 * 
 * <ul>
 * <li><a href="http://www.w3schools.com/xhtml/">A simple XHTML overview</a></li>
 * <li><a href="http://www.w3schools.com/tags/">A list of (X)HTML tags</a>
 * with links to valid attributes for each tag</li>
 * <li><a href="http://validator.w3.org/">W3C Markup Validation Service</a>
 * for checking any (X)HTML document</li>
 * </ul>
 * 
 * @since 2007-06-02
 */
public abstract class AbstractXhtmlWriter extends PrintWriter {
	
	/**
	 * Name of the file being generated. If the file getting generated is
	 * "java/lang/Object.html", then the filename is "Object.html".
	 */
	protected String filename = "";
	
	/**
	 * Platform-dependent directory path from the current or the destination
	 * directory to the file getting generated. Used when creating the file. For
	 * example, if the file being generated is "java/lang/Object.html", the path
	 * string is "java/lang".
	 */
	protected String path = "";
	
	/** URI path separator string. */
	public static final char SLASH = File.separatorChar;
	
	/**
	 * Document Type Declaration for HTML 4.01 Transitional pages.
	 * 
	 * @deprecated New web pages should use {@link #DTD_XHTML1_TRANS} instead.
	 */
	public static final String DTD_HTML4_LOOSE =
			"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" "
					+ "\"http://www.w3.org/TR/html4/loose.dtd\">";
	
	/** Document Type Declaration for XHTML 1.0 Transitional pages. */
	public static final String DTD_XHTML1_TRANS =
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
					+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
	
	/** Document Type Declaration for XHTML 1.0 Strict pages. */
	public static final String DTD_XHTML1_STRICT =
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
					+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";
	
	/** Document Type Declaration for XHTML 1.0 Frameset pages. */
	public static final String DTD_XHTML1_FRAMES =
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" "
					+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\">";
	
	/**
	 * Standard &lt;html&gt; tag that includes the attributes {xmlns, xml,
	 * lang}.
	 */
	public static final String HTML_XML =
			"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
	
	/** Standard &lt;meta&gt; tag that includes declaration of the UTF-8 charset. */
	public static final String META_UTF8 =
			"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />";
	
	/**
	 * Constructor that initializes PrintWriter with a file destination.
	 * 
	 * @param path The directory path to be created for this file, null if none.
	 * @param filename Filename to which the PrintWriter should send the output.
	 * @param encoding Encoding to be used for this file.
	 * @exception IOException If {@link FileOutputStream} creation fails.
	 */
	protected AbstractXhtmlWriter(String path, String filename, String encoding)
			throws IOException {
		super(AbstractXhtmlWriter.createOutputStream(path, filename));
		this.path = path;
		this.filename = filename;
	}
	
	/**
	 * Method for initializing a Writer to be used within a class instance.
	 * 
	 * @param path The directory path to be created for this file, null if none.
	 * @param filename Filename to which the PrintWriter should send the output.
	 * @return An stream for writing to a named file at the specified path.
	 * @throws IOException If creation of directory or FileOutputStream fails.
	 */
	protected static FileOutputStream createOutputStream(String path,
			String filename) throws IOException {
		if (path != null) {
			AbstractXhtmlWriter.createDirectory(path);
			String filePathPrefix = (path.length() > 0) ? path + SLASH : "";
			return new FileOutputStream(filePathPrefix + filename);
		} else {
			return new FileOutputStream(filename);
		}
	}
	
	/**
	 * Creates all the directories in a given path string. For example, if the
	 * path string is "java/applet", the method will create directory "java" and
	 * then "java/applet" if they don't exist. The file separator string "/" is
	 * a platform-dependent system property.
	 * 
	 * @param path Directory path string.
	 * @throws IOException If creation of the directory fails for some reason.
	 */
	protected static void createDirectory(String path) throws IOException {
		if (path == null || path.length() == 0) {
			return;
		}
		File dir = new File(path);
		// If the directories for the path don't exist, attempt to create them
		if (!dir.exists()) {
			if (!dir.mkdirs()) {
				System.out.println("Unable to create directory: " + path);
				throw new IOException();
			}
			// Otherwise, directory creation was successful
		}
	}
	
	/**
	 * Create a self-closing XHTML tag.
	 * 
	 * @param tagContents Whatever text should appear within the tag.
	 * @return <code>&lt;tagContents /&gt;</code>
	 */
	public String empty(String tagContents) {
		return "<" + tagContents + " />";
	}
	
	/**
	 * Create a self-closing XHTML tag with a map of attributes.
	 * 
	 * @param tagName The name to be given to the tag.
	 * @param attributes A {@link java.util.Map} of the attribute-value pairs
	 *        for the tag.
	 * @return <code>&lt;tagName key1="val1" ... keyN="valN" /&gt;</code>
	 */
	public String empty(String tagName, Map<String, String> attributes) {
		return "<" + tagName + attributeStringFromMap(attributes) + " />";
	}
	
	/**
	 * Create an opening XHTML tag.
	 * 
	 * @param tagContents Whatever text should appear within the tag.
	 * @return <code>&lt;tagContents&gt;</code>
	 */
	public String open(String tagContents) {
		return "<" + tagContents + ">";
	}
	
	/**
	 * Create an opening XHTML tag with a map of attributes.
	 * 
	 * @param tagName The name to be given to the tag.
	 * @param attributes A {@link Map} of the attribute key-value pairs for the
	 *        tag.
	 * @return <code>&lt;tagName key1="val1" ... keyN="valN"&gt;</code>
	 */
	public String open(String tagName, Map<String, String> attributes) {
		return "<" + tagName + attributeStringFromMap(attributes) + ">";
	}
	
	/**
	 * Create a closing XHTML tag.
	 * 
	 * @param tagName The name to be given to this tag.
	 * @return <code>&lt;/tagContents&gt;</code>
	 */
	public String close(String tagName) {
		return "</" + tagName + ">";
	}
	
	/**
	 * Creates a properly-formatted string from a group of attribute-value
	 * pairs.
	 * 
	 * @param attributes A {@link Map} of attribute key-value pairs.
	 * @return "<code> key1="val1" ... keyN="valN"</code>" (includes a
	 *         leading space)
	 * @see #open(String,Map)
	 * @see #empty(String,Map)
	 */
	protected String attributeStringFromMap(Map<String, String> attributes) {
		StringBuilder sb = new StringBuilder();
		/*
		 * Iterator<String> iter = attributes.keySet().iterator(); String key;
		 * while (iter.hasNext()) { key = iter.next(); sb.append(" " + key +
		 * "=\"" + attributes.get(key) + "\""); }
		 */
		// Escape several values to create valid (X)HTML
		for (Map.Entry<String, String> entry : attributes.entrySet()) {
			sb.append(" " + entry.getKey() + "=\""
					+ escapeHTML(entry.getValue()) + "\"");
		}
		return sb.toString();
	}
	
	/**
	 * Replace characters used in HTML markup with their HTML entity
	 * equivalents.
	 * 
	 * @param input The text to be processed for escapable values.
	 * @return An HTML-sanitized String, replace markup symbols.
	 */
	protected String escapeHTML(String input) {
		String output = input;
		output = output.replaceAll("&", "&amp;");
		output = output.replaceAll("<", "&lt;");
		output = output.replaceAll(">", "&gt;");
		output = output.replaceAll("\"", "&quot;");
		output = output.replaceAll("'", "&apos;");
		return output;
	}
	
	/**
	 * Create an HTML definition list for a term and single definition value.
	 * 
	 * @param term The term to be wrapped in DT tags.
	 * @param definition The definition to be wrapped in DD tags.
	 * @return String of the format:
	 *         <code>&lt;dl&gt;&lt;dt&gt;term&lt;/dl&gt;&lt;dd&gt;definition&lt;/dd&gt;&lt;/dl&gt;</code>
	 * 
	 * @see #definitionList(String, String[])
	 */
	protected final String definitionList(String term, String definition) {
		return definitionList(term, new String[] {definition});
	}
	
	/**
	 * Create an HTML definition list for a term and set of definition values.
	 * 
	 * @param term The term to be wrapped in DT tags within the list.
	 * @param definitions A set of definitions to be wrapped in DD tags. If null
	 *        or empty, no DD tags are printed.
	 * @return String of the format:
	 *         <code>&lt;dl&gt;&lt;dt&gt;term&lt;/dl&gt;{&lt;dd&gt;definition&lt;/dd&gt;}*&lt;/dl&gt;</code>
	 */
	protected final String definitionList(String term, String[] definitions) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(open("dl") + "\n");
		buffer.append(open("dt") + term + close("dt") + "\n");
		if (definitions != null)
			for (String definition : definitions)
				buffer.append(open("dd") + definition + close("dd") + "\n");
		buffer.append(close("dl"));
		return buffer.toString();
	}
	
	/**
	 * Create an opening DIV tag with a class attribute for CSS.
	 * 
	 * @param classValue The CSS class name to associate with the DIV.
	 * @return <code>&lt;div class="cssClass"&gt;</code>
	 */
	protected final String openDivWithClass(String classValue) {
		return "<div class=\"" + classValue + "\">";
	}
	
	/**
	 * Create an opening DIV tag with an id attribute.
	 * 
	 * @param idValue The id value to associate with the DIV.
	 * @return <code>&lt;div id="idValue"&gt;</code>
	 */
	protected final String openDivWithID(String idValue) {
		return "<div id=\"" + idValue + "\">";
	}
	
	/**
	 * Create an opening UL tag with an id attribute.
	 * 
	 * @param idValue The id value to associate with the UL.
	 * @return <code>&lt;ul id="idValue"&gt;</code>
	 */
	protected final String openULWithID(String idValue) {
		return "<ul id=\"" + idValue + "\">";
	}
	
	/**
	 * A line break tag that is hidden using CSS. The objective is to make it
	 * invisible for standards-compliant browsers, but make text more readable
	 * if CSS is not supported.
	 * 
	 * @return <code>&lt;br style="display: none;" /&gt;</code>
	 */
	protected final String hiddenBR() {
		return "<br style=\"display: none;\" />";
	}
	
	/**
	 * Convenience method for creating a target composed of a destination page
	 * and anchor.
	 * 
	 * @param page An HTML page to link to.
	 * @param anchor An anchor within the page to jump to.
	 * @return <code>page#anchor</code> (if both arguments are valid)<br />
	 *         <code>page</code> (if anchor is null or empty string)<br />
	 *         <code>#anchor</code> (if page is null or empty string)<br />
	 *         Empty string if both page and anchor are null or empty string
	 */
	protected final String hrefForPageAnchor(String page, String anchor) {
		return "" + ((page != null) ? page : "")
				+ ((anchor != null && !anchor.equals("")) ? "#" + anchor : "");
	}
	
	/**
	 * Wrap the parameter in &lt;li&gt; tags.
	 * 
	 * @param innerText The text to appear inside a list item
	 * @return "&lt;li&gt;innerText&lt;/li&gt;"
	 */
	protected final String listItem(String innerText) {
		return "<li>" + innerText + "</li>";
	}
	
	/**
	 * Create an anchor tag to an href, wrapped around the given label. If the
	 * href is null, the label is returned.
	 * 
	 * @param label The text to include between anchor tags.
	 * @param href The hypertext reference the link should point to.
	 * @return String of format:
	 *         <code>&lt;a href="href"&gt;label&lt;/a&gt;</code>
	 */
	protected final String linkToLabelHref(String label, String href) {
		if (href == null)
			return label;
		return new TagBuilder("a").add("href", href).getOpenTextClose(label);
	}
	
	/**
	 * Create a named anchor tag to an href, wrapped around the given label.
	 * 
	 * @param label The text to include between anchor tags.
	 * @param href The hypertext reference the link should point to.
	 * @param name The value to assign to the name attribute.
	 * @return String of format:
	 *         <code>&lt;a href="href" name="name"&gt;label&lt;/a&gt;</code>
	 */
	protected final String linkToLabelHrefName(String label, String href,
			String name) {
		return new TagBuilder("a").add("href", href).add("name", name).getOpenTextClose(
				label);
	}
	
	/**
	 * Create an anchor tag with popup title to an href, wrapped around the
	 * given label.
	 * 
	 * @param label The text to include between anchor tags.
	 * @param href The hypertext reference the link should point to.
	 * @param title The value to assign to the title attribute.
	 * @return String of format:
	 *         <code>&lt;a href="href" title="title"&gt;label&lt;/a&gt;</code>
	 */
	protected final String linkToLabelHrefTitle(String label, String href,
			String title) {
		return new TagBuilder("a").add("href", href).add("title", title).getOpenTextClose(
				label);
	}
	
	/**
	 * Create an anchor tag to open an href in a given pane, wrapped around the
	 * given label.
	 * 
	 * @param label The text to include between anchor tags.
	 * @param href The hypertext reference the link should point to.
	 * @param target Where the linked document will be opened.
	 * @return String of format:
	 *         <code>&lt;a href="href" target="target"&gt;label&lt;/a&gt;</code>
	 */
	protected final String linkToLabelHrefTarget(String label, String href,
			String target) {
		return new TagBuilder("a").add("target", target).add("href", href).getOpenTextClose(
				label);
	}
	
	/**
	 * Create an anchor tag with popup title to open an href in a given pane,
	 * wrapped around the given label.
	 * 
	 * @param label The text to include between anchor tags.
	 * @param href The hypertext reference the link should point to.
	 * @param target Where the linked document will be opened.
	 * @param title The value to assign to the title attribute.
	 * @return String of format:
	 *         <code>&lt;a href="href" target="target" title="title"&gt;label&lt;/a&gt;</code>
	 */
	protected final String linkToLabelHrefTargetTitle(String label,
			String href, String target, String title) {
		return new TagBuilder("a").add("href", href).add("target", target).add(
				"title", title).getOpenTextClose(label);
	}
	
	/**
	 * Create a standard opening JavaScript tag.
	 * 
	 * @return &lt;script language="JavaScript" type="text/javascript"&gt;
	 */
	protected final String scriptOpenJavascript() {
		return new TagBuilder("script").add("language", "JavaScript").add(
				"type", "text/javascript").getOpen();
	}
	
	/**
	 * Create a string with the requested number of spaces.
	 * 
	 * @param length The number of spaces to include in the string
	 * @param nonBreaking Whether the spaces should be an &amp;nbsp;
	 * @return A string with <code>length</code> number of spaces.
	 */
	protected String spaces(int length, boolean nonBreaking) {
		StringBuffer spaces = new StringBuffer();
		for (int i = 0; i < length; i++)
			spaces.append((nonBreaking) ? "&nbsp;" : " ");
		return spaces.toString();
	}
	
	/**
	 * Print text within &lt;dd&gt; tags, unless null or empty. *
	 * 
	 * @param text The text to display between <code>dd</code> tags.
	 */
	protected void printDD(String text) {
		if (text != null && !text.equals(""))
			println(open("dd") + text + close("dd"));
		else if (text.equals(""))
			println(open("dd") + "&nbsp;" + close("dd"));
	}
	
	/**
	 * Print text within DT tags, unless null or empty. *
	 * 
	 * @param text The text to display between <code>dd</code> tags.
	 */
	protected void printDT(String text) {
		if (text != null && !text.equals(""))
			println(open("dt") + text + close("dt"));
		else if (text.equals(""))
			println(open("dt") + "&nbsp;" + close("dt"));
	}
	
	/**
	 * Prints text in the format:
	 * <code>&lt;dt class="cssClass"&gt;text&lt;/dt&gt;</code> .
	 * 
	 * @param text The text to display between <code>dt</code> tags.
	 * @param cssClass The CSS class name to associate with the dt.
	 */
	protected void printDTWithClass(String text, String cssClass) {
		println(open("dt class=\"" + cssClass + "\"") + text + close("dt"));
	}
	
	/**
	 * Prints text in the format: <code>&lt;th&gt;text&lt;/th&gt;</code> .
	 * 
	 * @param text The text to display between <code>th</code> tags.
	 */
	protected void printTH(String text) {
		println(open("th") + text + close("th"));
	}
	
	/**
	 * Prints text in the format: <code>&lt;td&gt;text&lt;/td&gt;</code> .
	 * 
	 * @param text The text to display between <code>td</code> tags.
	 */
	protected void printTD(String text) {
		println(open("td") + text + close("td"));
	}
	
	/**
	 * Prints text in the format:
	 * <code>&lt;td class="cssClass"&gt;text&lt;/td&gt;</code> .
	 * 
	 * @param text The text to display between <code>td</code> tags.
	 * @param cssClass The CSS class name to associate with the td.
	 */
	protected void printTDWithClass(String text, String cssClass) {
		println(open("td class=\"" + cssClass + "\"") + text + close("td"));
	}
	
	/**
	 * Wraps a given object's text in HTML <code>bold</code> tags.
	 * 
	 * @param obj The object to render as a String between tags.
	 * @return <code>&lt;bold&gt;obj.toString()&lt;/bold&gt;</code>
	 */
	protected String bold(Object obj) {
		return new TagBuilder("strong").getOpenTextClose(obj.toString());
	}
	
	/**
	 * Wraps a given object's text in HTML comment tags.
	 * 
	 * @param innerText The text to include between comment tags.
	 * @return <code>&lt;!-- obj.toString() --&gt;</code>
	 */
	protected final String getComment(String innerText) {
		return "<!-- " + innerText + " -->";
	}
	
	/**
	 * Concatenates a Set of strings using a specified separator string.
	 * 
	 * @param strings An array of strings to be joined.
	 * @param separator The separator to use for joining the strings.
	 * @return A joined string, or null if either argument is null.
	 * @see #getJoinedStrings(Object[], String)
	 */
	protected final String getJoinedStrings(List<String> strings,
			String separator) {
		return (strings == null) ? null : getJoinedStrings(
				strings.toArray(new String[] {}), separator);
	}
	
	/**
	 * Concatenates an array of strings using a specified separator string.
	 * Calls <code>toString()</code> on each array element before appending.
	 * 
	 * @param elements An array of objects to be joined in String form.
	 * @param separator The separator to use for joining the strings.
	 * @return A joined string, or null if either argument is null.
	 */
	protected final String getJoinedStrings(Object[] elements, String separator) {
		if (elements == null || separator == null)
			return null;
		StringBuffer joinedStrings = new StringBuffer();
		boolean first = true;
		for (Object obj : elements) {
			if (!first)
				joinedStrings.append(separator);
			joinedStrings.append(obj.toString());
			first = false;
		}
		return joinedStrings.toString();
	}
	
	/**
	 * Create a comment for denoting the beginning and end of document sections.
	 * 
	 * @param sectionName The text to include in the comment (may be any string)
	 * @return <code>&lt;!-- =========== sectionName =========== --&gt;</code>
	 */
	protected final String getSectionDelimiter(String sectionName) {
		return "<!-- =========== " + sectionName + " =========== -->";
	}
	
	/**
	 * Get the day and date information for today, dependent upon user option.
	 * 
	 * @return Today's date information.
	 * @see java.util.GregorianCalendar
	 * @see java.util.TimeZone
	 */
	protected final String today() {
		return new GregorianCalendar(TimeZone.getDefault()).getTime().toString();
	}
	
	/**
	 * <p>
	 * Provides a friendly interface to the {@link AbstractXhtmlWriter} methods
	 * for tag creation. A TagBuilder object can be used to accumulate tag
	 * information without worrying about low-level details. Since calling
	 * {@link #add(String,String)} either adds the new attribute and value or
	 * overwrites the existing value, a single object can be used multiple
	 * times, even for multiple tags by using {@link #setName(String)}, etc.
	 * </p>
	 * 
	 * <p>
	 * Sample usage and output might include:
	 * </p>
	 * <blockquote><code>
	 TagBuilder builder = new TagBuilder("a");<br />
	 builder.add("href", "../index.html");<br />
	 builder.add("title", "Return to site index");<br />
	 builder.add("style", "color: red;");<br />
	 System.out.println(builder.getOpenTextClose("Back Home"));
	 <br /><br />
	 <em>&lt;a href="../index.html" title="Return to site index"
	 style="color: red;"&gt;Back Home&lt;/a&gt;</em></code></blockquote>
	 * 
	 * <p>
	 * Alternatively, it is possible to create a TagBuilder, add attributes, and
	 * grab the result all inline if desired. For example:
	 * </p>
	 * <blockquote><code>System.out.println(new
	 TagBuilder("a").add("href","index.html").getOpen());
	 <br /><br />
	 <em>&lt;a href="../index.html"&gt;</em></code></blockquote>
	 * 
	 * <p>
	 * Currently, attribute-value pairs are stored in a
	 * {@link java.util.LinkedHashMap}.
	 * 
	 * @author Quinn Taylor
	 * @since 2007-06-02
	 */
	public class TagBuilder {
		private String tagName;
		
		private Map<String, String> attributes;
		
		/**
		 * Initializes a TagBuilder object with the specified name and empty
		 * attributes. The parameter should be a valid tag name (not null or an
		 * empty string), but this is not enforced in order to make dealing with
		 * the class simpler.
		 * 
		 * @param tagName The name to give to the tag being created.
		 * @see #clear
		 * @see #setName(String)
		 */
		public TagBuilder(String tagName) {
			this.tagName = tagName;
			attributes = new java.util.LinkedHashMap<String, String>();
		}
		
		/**
		 * Copy constructor for creating a new instance from an existing one.
		 * Creates A new copy referencing the same tag name but a carbon copy of
		 * the attribute map.
		 * 
		 * @param builder The TagBuilder instance to be copied
		 */
		public TagBuilder(TagBuilder builder) {
			tagName = builder.tagName;
			attributes =
					new java.util.LinkedHashMap<String, String>(
							builder.attributes);
		}
		
		/**
		 * <p>
		 * Add an attribute-value pair to the tag being built. If the attribute
		 * by that name has already been added, the old value is overwritten.
		 * </p>
		 * <p>
		 * Adding is not done if either parameter is null or attribute is empty.
		 * </p>
		 * 
		 * @param attribute A new attribute name.
		 * @param value A new attribute value.
		 * @return A reference to this object for inlining method calls.
		 * @see #remove(String)
		 * @see #clear()
		 */
		public TagBuilder add(String attribute, String value) {
			if (attribute != null && !attribute.equals("") && value != null)
				attributes.put(attribute.toLowerCase(), value);
			return this;
		}
		
		/**
		 * Removes the matching attribute-value pair if present.
		 * 
		 * @param attribute Attribute whose value is to be removed.
		 * @return A reference to this object for inlining method calls.
		 */
		public TagBuilder remove(String attribute) {
			attributes.remove(attribute);
			return this;
		}
		
		/**
		 * Clears all accumulated attributes so a new set of pairs can be
		 * specified. Calling this method is not necessary before creating a
		 * matching closing tag.
		 * 
		 * @return A reference to this object for inlining method calls.
		 */
		public TagBuilder clear() {
			attributes.clear();
			return this;
		}
		
		/**
		 * Replaces the existing tag name. (The argument shouldn't be null or
		 * empty.)
		 * 
		 * @param newName The name to use for a tag being built.
		 * @return A reference to this object for inlining method calls.
		 */
		public TagBuilder setName(String newName) {
			this.tagName = newName;
			return this;
		}
		
		/**
		 * Creates an empty tag with specified name and any attributes.
		 * 
		 * @return <code>&lt;tagName attr1="val1" ... attrN="valN" /&gt;</code>
		 * @see #empty(String,Map)
		 */
		public String getEmpty() {
			return empty(tagName, attributes);
		}
		
		/**
		 * Creates an opening tag with specified name and any attributes.
		 * 
		 * @return <code>&lt;tagName attr1="val1" ... attrN="valN"&gt;</code>
		 * @see #open(String,Map)
		 */
		public String getOpen() {
			return open(tagName, attributes);
		}
		
		/**
		 * Creates a closing tag with specified name only.
		 * 
		 * @return <code>&lt;/tagName&gt;</code>
		 * @see #close(String)
		 */
		public String getClose() {
			return close(tagName);
		}
		
		/**
		 * Creates an opening tag with attributes, inner text, and closing tag.
		 * 
		 * @param text The text that should be sandwiched between the open and
		 *        close tags.
		 * @return <code>&lt;tag attr1="val1" ... attrN="valN" &gt;text&lt;/tag&gt;</code>
		 * @see #open(String,Map)
		 * @see #close(String)
		 */
		public String getOpenTextClose(String text) {
			return open(tagName, attributes) + text + close(tagName);
		}
		
	}
	
}
